# 抽象类 & 接口
# abstract
# 简介
继承是用来表示类之间的 is-a 或is-like-a关系。随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
- 抽象方法:没有方法主体(没有大括号)的方法,用
abstract修饰,;结束 - 抽象类:包含抽象方法的类必须声明为抽象类,用
abstract修饰,但抽象类可以不包含任何抽象方法- 抽象类不能被实例化(可能包含抽象方法,但没有方法体无法调用)
- 可以创建子类继承
(extends)并重写父类所有抽象方法,否则该子类须声明为抽象类(最终必有实现类否则无意义)
# 抽象类成员特点
可以有类的一切属性
- 成员变量:有变量,有常量
- 成员方法:有抽象,有非抽象
- 构造方法:有,用于子类创建对象时初始化父类成员(因为子类构造中,有默认的
super()) - 静态代码块:可以有
# 注意事项
abstract 使用时注意:
- abstract 不能修饰属性、构造器等结构;
- abstract 不能修饰私有方法(private,私有方法属于类不能被重写)、静态方法(static,静态方法属于类不能被重写)、final方法、final类;
匿名子类对象/匿名子类的匿名对象(new的并不是抽象类!)
// 1 创建了一匿名子类的对象:p,其中Person为抽象类 Person p = new Person(){ @Override public void eat() { System.out.println("吃东西"); } @Override public void breath() { System.out.println("好好呼吸"); } }; method1(p);// 2 创建匿名子类的匿名对象 method1(new Person(){ @Override public void eat() { System.out.println("吃好吃东西"); } @Override public void breath() { System.out.println("好好呼吸新鲜空气"); } });
# interface
# 简介
继承是用来表示类之间的 is-a 或is-like-a关系
而接口实现则是 "能不能"的关系,接口的本质是标准,协议(protocol),规范。
Java中接口是多个类的公共规范标准,是方法的集合。是引用数据类型,用interface修饰,也会被编译成.class文件。接口中没有构造器。即不可以被实例化,需要定义一个类implements接口中所有方法,如果这个类是抽象类,实现部分即可。接口的具体使用,也体现多态性
# Java 7 接口
- 接口中成员变量必须是**全局常量(赋值且静态)**的,默认修饰符
public static final(大写、下划线),可省略。Java 5 以后使用枚举类替代了。 - 接口中成员方法必须是抽象方法,默认修饰符
public abstract,可省略
# Java 8 接口
默认方法(default,之前只用于 switch 语句和注解中)
供实现类直接调用或者重写。可以解决接口升级问题、拼接函数模型。Stream 中常使用
/*public*/ default 返回值类型 method(/*参数列表*/) { //default不可省略,public可省略 //方法体 }静态方法(static)
只能接口直接调用,不能用接口实现类对象来调用(因为可能实现多接口)。用于通用的工具
/*public*/ static 返回值类型 method(/*参数列表*/) { //static不可省略,public可省略 //方法体 }
默认方法和多继承
在 Java 8 之前,接口没有包袱——它只是方法的描述。Java 8 通过默认方法具有了某种多继承的特性。结合带有默认方法的接口意味着结合了多个基类中的行为。因为接口中仍然不允许存在属性(只有静态属性,不适用),所以属性仍然只会来自单个基类或抽象类,也就是说,不会存在状态的多继承。正如下面这样:
interface One {
default void first() {
System.out.println("first");
}
}
interface Two {
default void second() {
System.out.println("second");
}
}
interface Three {
default void third() {
System.out.println("third");
}
}
class MI implements One, Two, Three {}
public class MultipleInheritance {
public static void main(String[] args) {
MI mi = new MI();
mi.first();// first
mi.second();// second
mi.third();// third
}
}
现在我们做些在 Java 8 之前不可能完成的事:结合多个源的实现。只要基类方法中的方法名或参数列表不同,就能工作得很好,否则会得到编译器错误:
interface Bob1 {
default void bob() {
System.out.println("Bob1::bob");
}
}
interface Bob2 {
default void bob() {
System.out.println("Bob2::bob");
}
}
// class Bob implements Bob1, Bob2 {}
/* Produces:
error: class Bob inherits unrelated defaults
for bob() from types Bob1 and Bob2
class Bob implements Bob1, Bob2 {}
^
1 error
*/
interface Sam1 {
default void sam() {
System.out.println("Sam1::sam");
}
}
interface Sam2 {
default void sam(int i) {
System.out.println(i * 2);
}
}
// This works because the argument lists are distinct:
class Sam implements Sam1, Sam2 {}
interface Max1 {
default void max() {
System.out.println("Max1::max");
}
}
interface Max2 {
default int max() {
return 47;
}
}
// class Max implements Max1, Max2 {}
/* Produces:
error: types Max2 and Max1 are imcompatible;
both define max(), but with unrelated return types
class Max implements Max1, Max2 {}
^
1 error
*/
Sam 类中的两个 sam() 方法有相同的方法名但是签名不同——方法签名包括方法名和参数类型,编译器也是用它来区分方法。但是从 Max 类可看出,返回类型不是方法签名的一部分,因此不能用来区分方法。为了解决这个问题,需要覆写冲突的默认方法:
interface Jim1 {
default void jim() {
System.out.println("Jim1::jim");
}
}
interface Jim2 {
default void jim() {
System.out.println("Jim2::jim");
}
}
public class Jim implements Jim1, Jim2 {
@Override
public void jim() {
Jim2.super.jim();
}
public static void main(String[] args) {
new Jim().jim();// Jim2::jim
}
}
当然,你可以重定义 jim() 方法,但是也能像上例中那样使用 super 关键字选择基类实现中的一种。语法为InterfaceA.super.method()
# Java 9 接口
私有方法(private):向上抽取重复代码,只能供接口自己中的默认方法或者静态方法调用
private 返回值类型 method(/*参数列表*/) { //方法体 }private static 返回值类型 method(/*参数列表*/) { //方法体 }
# 笔记本USB接口案例

# 注意
- 接口没有静态代码块和构造方法(其实现类继承
Object,提供无参构造) - 一个类的直接父类是唯一的,但一个类可以实现多个接口(用
,隔开) - 实现类的直接父类中的方法和接口中默认方法产生冲突,在子类没有重写情况下优先使用父类中的方法。类优先原则
- 实现类实现的多个接口中,存在重复的抽象方法(方法签名相同),那么只需重写一次即可
- Java8 接口中定义的静态方法,只能通过接口来调用,因为可能实现多接口
- 通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口中的默认方法,调用时调用的是重写以后的方法
- 实现类实现的多个接口中,存在重复的默认方法(方法签名相同),存在接口冲突
- 选择其中一个冲突方法,重写时调用它即可,语法为
InterfaceA.super.method() - 必须重写冲突的默认方法
- 选择其中一个冲突方法,重写时调用它即可,语法为
- 接口的权限修饰同普通类
# 类与接口关系总结
类与类:继承关系,只能单继承,可以多层继承
类与接口:实现关系,可以单实现,也可以多实现,可以在继承一个类时实现多个接口
接口与接口:继承关系,可以单继承,也可以多继承。可不用全部实现抽象方法
多继承中,如果父接口中的默认方法(抽象方法)有重名的,那么子接口须重写一次。
同抽象类一样,接口也可以创建:非匿名实现类的非匿名对象、非匿名实现类的匿名对象、匿名实现类的非匿名对象、匿名实现类的匿名对象
# 抽象类和接口
尤其是在 Java 8 引入 default 方法之后,选择用抽象类还是用接口变得更加令人困惑。下表做了明确的区分:
| 特性 | 接口 | 抽象类 |
|---|---|---|
| 组合 | 新类可以组合多个接口 | 只能继承单一抽象类 |
| 状态 | 不能包含属性(除了静态属性,不支持对象状态) | 可以包含属性,非抽象方法可能引用这些属性 |
| 默认方法 和 抽象方法 | 不需要在子类中实现默认方法。默认方法可以引用其他接口的方法 | 必须在子类中实现抽象方法 |
| 构造器 | 没有构造器 | 可以有构造器 |
| 可见性 | 隐式 public | 可以是 protected 或友元 |
抽象类仍然是一个类,在创建新类时只能继承它一个。而创建类的过程中可以实现多个接口。
有一条实际经验:尽可能地抽象。因此,更倾向使用接口而不是抽象类。只有当必要时才使用抽象类。除非必须使用,否则不要用接口和抽象类。大多数时候,普通类已经做得很好,如果不行的话,再移动到接口或抽象类中。